<?php

/**
 * QPHP
 *
 * Copyright (c) 2022 QPHP
 *
 * @author lbnnbs
 * @copyright Copyright (c) 2022 lbnnbs QPHP (https://gitee.com/lbnnbs/qphp)
 * @license AGPLv3 License
 * 
 * 加解密组件
 */
class Mcrypt {

    /**
     * AES 块长度
     */
    const BLOCK_SIZE = 16;
    
    /**
     * 不进行base64编码
     */
    const OPENSSL_RAW_DATA = 1;

    /**
     * 防止类实例化或被复制
     *
     */
    private function __construct() {
        
    }

    private function __clone() {
        
    }

    private static function preCondition() {
        if (!in_array('aes-256-gcm', openssl_get_cipher_methods())) {
            throw new RuntimeException('openssl 扩展缺失 aes-256-gcm 方法');
        }
    }

    /**
     * 加密
     *
     * @param string $plaintext - 明文
     * @param string $key - 秘钥，32位字符串
     * @param string $aad - 附加数据，可选，解密时传入相同的数据用于鉴别数据来源
     * 
     * @return string - 密文.
     */
    public static function encrypt($plaintext, $key, $aad = '') {
        self::preCondition();

        $iv = randString(static::BLOCK_SIZE); // 初始化向量，16位字符串
        
        $tag = $aad = '';
        
        $ciphertext = openssl_encrypt($plaintext, 'aes-256-gcm', $key, static::OPENSSL_RAW_DATA, $iv, $tag, $aad, static::BLOCK_SIZE);

        if (false === $ciphertext) {
            throw new UnexpectedValueException('加密失败，请检查秘钥是否正确');
        }

        return base58_encode($ciphertext . $tag . $iv);
    }

    /**
     * 解密
     *
     * @param string $ciphertext - 密文
     * @param string $key - 秘钥，32位字符串
     * @param string $aad - 附加数据，可选，传入和加密时相同的数据用于鉴别数据来源
     *
     * @return string - 明文
     */
    public static function decrypt($ciphertext, $key, $aad = '') {
        self::preCondition();

        $ciphertext = base58_decode($ciphertext);

        $tailLength = 0 - static::BLOCK_SIZE;

        $iv = substr($ciphertext, $tailLength); // 初始化向量，16位字符串

        $ciphertext = substr($ciphertext, 0, $tailLength); // 去掉向量后的密文，含Tag

        $authTag = substr($ciphertext, $tailLength);  // $tag 是openssl_encrypt产生的数据完整性校验字符串

        $tagLength = strlen($authTag);

        /* 检查tag长度，openssl_decrypt 前需要做校验，因为substr后得到的字符串长度可能不足一个BLOCK_SIZE */
        if ($tagLength > static::BLOCK_SIZE || ($tagLength < 12 && $tagLength !== 8 && $tagLength !== 4)) {
            throw new RuntimeException('密文不完整, 长度必须是 16, 15, 14, 13, 12, 8, 4位');
        }

        $ciphertext = substr($ciphertext, 0, $tailLength); // 去掉向量和Tag后的纯密文

        $plaintext = openssl_decrypt($ciphertext, 'aes-256-gcm', $key, static::OPENSSL_RAW_DATA, $iv, $authTag, $aad);

        if (false === $plaintext) {
            throw new UnexpectedValueException('解密失败，请检查秘钥是否正确');
        }

        return $plaintext;
    }
}
